/*--------------------------------------------- Noble Steed Engine--------------------------------------*
Authors : Daniel Randle, Alex Bourne
Date Created : Mar 13 2013

File:
	nsobjectreference.cpp

Description:
	This file contains the definition for the NSObjectReference class and any associated functions that help
	the ol' girl out
*-----------------------------------------------------------------------------------------------------*/

#include <nsobjectreference.h>
#include <nsobject.h>
#include <sstream>
#include <nsshader.h>
#include <nsshaderset.h>
#include <nsobjectset.h>
#include <nscamera.h>
#include <nslight.h>
#include <nsengine.h>
#include <nsmesh.h>
#include <nsboundingbox.h>

#ifdef DEBUG
#include <debug.h>
#endif

/*----------------------------------*
|   class NSObjectReference BEGINS  |
*-----------------------------------*/


NSObjectReference::NSObjectReference(NSObject * objPtr, const NSVec3Df & pos,const NSVec3Df & orient,const NSVec3Df & _scale):
	object(objPtr), 
	position(pos),
	orientation(orient),
	size(_scale),
	snap(true)
{
	InitializeCriticalSection(&lock);
	OBB = new NSBoundingBox();
	AABB = new NSBoundingBox(NSBoundingBox::AABB);
	add();
}

NSObjectReference::~NSObjectReference()
{
	delete OBB;
	delete AABB;
	DeleteCriticalSection(&lock);
}

void NSObjectReference::add()
{
	referenceID = object->getInstanceCount();
	NSMatrix4Df mat;
	mat.translate(position.x,position.y,position.z);
	mat.rotate(orientation.x, orientation.y, orientation.z);
	mat.scale(size.x, size.y, size.z);
	OBB->calculate(object->getMesh());
	AABB->calculate(object->getMesh(),mat);
	object->addInstanceTransform(mat);
}

void NSObjectReference::decrementID()
{
	--referenceID;
}

void NSObjectReference::drawOBB()
{
	OBB->draw(object->instanceTransMat(referenceID));
}

void NSObjectReference::drawAABB()
{
	AABB->draw();
}

NSBoundingBox * NSObjectReference::getAABB()
{
	return AABB;
}

NSBoundingBox * NSObjectReference::getOBB()
{
	return OBB;
}

NSObject * NSObjectReference::getBase()
{
	return object;
}

float NSObjectReference::getDepth(NSCamera * cam) const
{
	NSVec3Df dif = cam->getPosition(NSCamera::FREE) + position;
	NSVec3Df targetVec = cam->getUVNVec(NSCamera::NVector);
	float targetMagnitude = targetVec.length();
	float dotProduct = dif * targetVec;
	targetVec *= (dotProduct / (targetMagnitude*targetMagnitude));
	return targetVec.length();
}

unsigned int NSObjectReference::getID() const
{
	return referenceID;
}

NSVec3Df NSObjectReference::getMoveXYFromMouseNDC(const NSVec2Df & ndcCurrentMousePos, const NSVec2Df & ndcLastMousePos, NSCamera * camera)
{
	NSVec2Df ds = ndcCurrentMousePos - ndcLastMousePos;
	float depth = getDepth(camera);
	NSVec3Df orient = camera->getOrientation();
	int divisor = orient.x / 90.0f;

	if (divisor % 2 != 0)
		ds.y *= -1;

	NSVec4Df move(ds.x, ds.y,1.0f/depth,0.0f);
	move = move * depth;
	move =  inverseProjMat * move;
	move.y -= move.z;
	move = NSMatrix4Df::getRotationMatrix(0,0,-orient.z) * move;
	return NSVec3Df(move.x,move.y,0.0f);
}

NSVec3Df NSObjectReference::getMoveZFromMouseNDC(const NSVec2Df & ndcCurrentMousePos, const NSVec2Df & ndcLastMousePos, NSCamera * camera)
{
	NSVec2Df ds = ndcCurrentMousePos - ndcLastMousePos;
	float depth = getDepth(camera);
	NSVec3Df orient = camera->getOrientation();
	NSVec4Df move(ds.x, -ds.y,1.0f/depth,0.0f);
	move = move * depth;
	move = inverseProjMat * move;
	move.z -= move.y;
	move = NSMatrix4Df::getRotationMatrix(-orient.x,-orient.y,-orient.z) * move;
	return NSVec3Df(0,0,move.z);
}

NSVec3Df NSObjectReference::getOrientation() const
{
	return orientation;
}

NSVec3Df NSObjectReference::getPosition() const
{
	return position;
}

NSMatrix4Df NSObjectReference::getPOVTransform() const
{
	NSMatrix4Df transform;
	NSVec3Df uVec(1.0f,0.0f,0.0f);
	NSVec3Df vVec(0.0f,1.0f,0.0f);
	NSVec3Df nVec(0.0f,0.0f,1.0f);
	uVec.rotate(orientation);
	vVec.rotate(orientation);
	nVec = uVec.cross(vVec);
	uVec = vVec.cross(nVec);
	uVec.normalize();
	vVec.normalize();
	nVec.normalize();
	transform.setData(uVec,vVec*-1,nVec*-1);
	transform.translate(position*-1);
	return transform;
}

NSVec3Df NSObjectReference::getScale() const
{
	return size;
}

bool NSObjectReference::isSnapToGrid()
{
	return snap;
}

void NSObjectReference::moveXYFromMouseNDC(const NSVec2Df & ndcCurrentMousePos, const NSVec2Df & ndcLastMousePos, NSCamera * camera)
{
	translate(getMoveXYFromMouseNDC(ndcCurrentMousePos,ndcLastMousePos,camera));
}

void NSObjectReference::moveZFromMouseNDC(const NSVec2Df & ndcCurrentMousePos, const NSVec2Df & ndcLastMousePos, NSCamera * camera)
{
	translate(getMoveZFromMouseNDC(ndcCurrentMousePos,ndcLastMousePos,camera));
}

void NSObjectReference::remove()
{
	object->removeInstanceTransform(referenceID);
}

void NSObjectReference::rotate(const NSVec3Df & amount)
{
	orientation += amount;
	update();
}

void NSObjectReference::rotate(float angX, float angY, float angZ)
{
	rotate(NSVec3Df(angX,angY,angZ));
}

void NSObjectReference::scale(const NSVec3Df & amount)
{
	size += amount;
	update();
}

void NSObjectReference::scale(float scaleX, float scaleY, float scaleZ)
{
	scale(NSVec3Df(scaleX,scaleY,scaleZ));
}

void NSObjectReference::setSnapToGrid(bool set)
{
	snap = set;
	snapToGrid();
}

void NSObjectReference::setProjectionMat(const NSMatrix4Df & mat)
{
	projectionMat = mat;
	inverseProjMat = projectionMat.getInverse();
}

void NSObjectReference::snapToGrid()
{
	int posxMult,posyMult,poszMult;
	float x,y,z;
	posyMult = floor((float(position.y) / float(1.49f * 1.0f))+0.5f);

	if (posyMult % 2 != 0)
		posxMult = floor(( (float(position.x)- 0.86f*1) / float(0.86f * 2.0f)) + 0.5f);
	else
		posxMult = floor((float(position.x) / float(0.86f * 2.0f))+0.5f);

	poszMult = floor((float(position.z) / float(.45 * 1.0f))+0.5f);
	x = (posxMult) * (0.86f * 2.0f);
	y = (posyMult) * (1.49f * 1.0f);
	z = (poszMult) * (.45 * 1.0f);
	if ((posyMult % 2) != 0)
		x += 0.86f*1;
	position.x = x;
	position.y = y;
	position.z = z;
	update();
}

void NSObjectReference::translate(const NSVec3Df & amount)
{
	position += amount;
	update();
}

void NSObjectReference::translate(float moveX, float moveY, float moveZ)
{
	translate(NSVec3Df(moveX,moveY,moveZ));
}

void NSObjectReference::update()
{
	NSMatrix4Df newTrans;
	newTrans.translate(position.x,position.y,position.z);
	newTrans.rotate(orientation.x, orientation.y, orientation.z);
	newTrans.scale(size.x,size.y,size.z);
	object->instanceTransMat(referenceID) = newTrans;
	AABB->update(object->getMesh(), newTrans);
}

void NSObjectReference::serialize(std::ofstream & out)
{
	Serialize ser;
	ser.position = position;
	ser.orientation = orientation;
	ser.size = size;
	ser.snap = snap;
	out.write((char*)&ser, sizeof(Serialize));
}

void NSObjectReference::deserialize(std::ifstream & in)
{
	Serialize ser;
	in.read((char*)&ser,sizeof(Serialize));

	position = ser.position;
	orientation = ser.orientation;
	size = ser.size;
	snap = ser.snap;
}

NSPointLightReference::NSPointLightReference(NSPointLight * objPtr,NSShaderSet * _shaders, const NSVec3Df & pos,const NSVec3Df & orient, const NSVec3Df & _scale):
	NSObjectReference(objPtr, pos, orient, _scale),
	shaders(_shaders)
{
	std::stringstream ss;
	ss << "Creating PointLight reference with ID " << referenceID;
	LogFile l(ss);
}

NSPointLightReference::~NSPointLightReference()
{
	std::stringstream ss;
	ss << "Deleting PointLight reference with ID " << referenceID;
	LogFile l(ss);
}

float NSPointLightReference::getLightScaleFactor()
{
	NSVec3Df col = ((NSPointLight*)object)->getColor();
	float dif = ((NSPointLight*)object)->getDiffuse();
    float maxC = max(max(col.x,col.y),col.z);
    float c = maxC * dif;

    return (32.0f * sqrtf(c) + 1.0f);
}

NSMatrix4Df NSPointLightReference::getLightTransform()
{
	NSMatrix4Df newTrans;
	float scaleF = getLightScaleFactor();
	newTrans.translate(position.x,position.y,position.z);
	newTrans.rotate(orientation.x, orientation.y, orientation.z);
	newTrans.scale(scaleF,scaleF,scaleF);
	return newTrans;
}

void NSPointLightReference::setShaderSet(NSShaderSet * _shaders)
{
	shaders = _shaders;
}

void NSPointLightReference::update()
{
	NSObjectReference::update();
}

NSSpotLightReference::NSSpotLightReference(NSSpotLight * objPtr,
	NSShaderSet * _shaders, 
	const NSVec3Df & pos,
	const NSVec3Df & orient, 
	const NSVec3Df & _scale):
	NSPointLightReference(objPtr,_shaders, pos, orient, _scale)
{
	std::stringstream ss;
	ss << "Creating SpotLight reference with ID " << referenceID;
	LogFile l(ss);
}

NSSpotLightReference::~NSSpotLightReference()
{
	std::stringstream ss;
	ss << "Deleting SpotLight reference with ID " << referenceID;
	LogFile l(ss);
}

void NSSpotLightReference::update()
{
	NSObjectReference::update();
}

float NSSpotLightReference::getLightScaleFactor()
{
	NSVec3Df col = ((NSSpotLight*)object)->getColor();
	float dif = ((NSSpotLight*)object)->getDiffuse();
    float maxC = max(max(col.x,col.y),col.z);
    float c = maxC * dif;

    return 16.0f * sqrtf(c) + 1.0f;
}

NSMatrix4Df NSSpotLightReference::getLightTransform()
{
	NSMatrix4Df newTrans;
	float cutoff = ((NSSpotLight*)object)->getCutoff();
	float scaleF = getLightScaleFactor();
	newTrans.translate(position.x,position.y,position.z);
	newTrans.rotate(orientation.x, orientation.y, orientation.z);
	newTrans.scale(scaleF*4,scaleF*4,scaleF);
	newTrans.translate(0.0f,0.0f,-1.0f);
	return newTrans;
}